/**
 * MIT License
 * 
 * Copyright (c) 2024 - present @ ebraid
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <stm32h7.h>
#include <config.h>
#include <mm/mem.h>


// PLLCLK_FREQ = (CLOCK_HSE_OSC_HZ / CLOCK_DIVM) * PLL_N / PLL_P
// AHB1: 240MHz
// AHB2: 240MHz
// AHB3: 240MHz
// AHB4: 240MHz
// APB1: 120MHz
// APB2: 120MHz
// APB3: 120MHz
// APB4: 120MHz


#define  CLOCK_HSE_OSC_HZ               (25000000)
#define  CLOCK_DIVM1                    (5)
#define  CLOCK_DIVM2                    (5)
#define  CLOCK_DIVM3                    (5)

#define  CLOCK_PLL1_DIVN                (192)
#define  CLOCK_PLL1_DIVP                (2)    // 1: /1     2: /2    3: /3   4: /4 ...
#define  CLOCK_PLL1_DIVQ                (4)    // 1: /1     2: /2    3: /3   4: /4 ...
#define  CLOCK_PLL1_DIVR                (1)    // 1: /1     2: /2    3: /3   4: /4 ...

#define  CLOCK_PLL2_DIVN                (192)
#define  CLOCK_PLL2_DIVP                (2)
#define  CLOCK_PLL2_DIVQ                (4)
#define  CLOCK_PLL2_DIVR                (1)

#define  CLOCK_PLL3_DIVN                (192)
#define  CLOCK_PLL3_DIVP                (2)
#define  CLOCK_PLL3_DIVQ                (4)
#define  CLOCK_PLL3_DIVR                (1)


/**
 *  @brief  SRAM memory information
*/
#define  SRAM_DTCM_START_ADDR           (0x20000000)
#define  SRAM_DTCM_SIZE                 (128 * 1024)
#define  SRAM_D1_START_ADDR             (0x24000000)
#define  SRAM_D1_SIZE                   (512 * 1024)
#define  SRAM_D2_START_ADDR             (0x30000400)
#define  SRAM_D2_SIZE                   (287 * 1024)
#define  SRAM_D3_START_ADDR             (0x38000000)
#define  SRAM_D3_SIZE                   (64 * 1024)
#define  SRAM_ITCM_START_ADDR           (0x00000000)
#define  SRAM_ITCM_SIZE                 (64 * 1024)


uint32_t system_core_clock = 480000000;


/**
 * @brief       时钟设置函数
 * @param       plln: PLL1倍频系数(PLL倍频), 取值范围: 4~512.
 * @param       pllm: PLL1预分频系数(进PLL之前的分频), 取值范围: 2~63.
 * @param       pllp: PLL1的p分频系数(PLL之后的分频), 分频后作为系统时钟, 取值范围: 2~128.(且必须是2的倍数)
 * @param       pllq: PLL1的q分频系数(PLL之后的分频), 取值范围: 1~128.
 * @note
 *
 *              Fvco: VCO频率
 *              Fsys: 系统时钟频率, 也是PLL1的p分频输出时钟频率
 *              Fq:   PLL1的q分频输出时钟频率
 *              Fs:   PLL输入时钟频率, 可以是HSI, CSI, HSE等.
 *              Fvco = Fs * (plln / pllm);
 *              Fsys = Fvco / pllp = Fs * (plln / (pllm * pllp));
 *              Fq   = Fvco / pllq = Fs * (plln / (pllm * pllq));
 *
 *              外部晶振为25M的时候, 推荐值: plln = 192, pllm = 5, pllp = 2, pllq = 4.
 *              得到:Fvco = 25 * (192 / 5) = 960Mhz
 *                   Fsys = pll1_p_ck = 960 / 2 = 480Mhz
 *                   Fq   = pll1_q_ck = 960 / 4 = 240Mhz
 *
 *              H750默认需要配置的频率如下:
 *              CPU频率(rcc_c_ck) = sys_d1cpre_ck = 480Mhz
 *              rcc_aclk = rcc_hclk3 = 240Mhz
 *              AHB1/2/3/4(rcc_hclk1/2/3/4) = 240Mhz
 *              APB1/2/3/4(rcc_pclk1/2/3/4) = 120Mhz
 *              pll2_p_ck = (25 / 25) * 440 / 2) = 220Mhz
 *              pll2_r_ck = FMC时钟频率 = ((25 / 25) * 440 / 2) = 220Mhz
 *
 * @retval      错误代码: 0, 成功; 1, HSE错误; 2, PLL1错误; 3, PLL2错误; 4, 切换时钟错误;
 */
static void clock_init(void)
{
    uint32_t retry = 0;
    uint8_t retval = 0;
    uint8_t swsval = 0;

    RCC->CR = 0x00000001;           /* 设置HISON, 开启内部高速RC振荡，其他位全清零 */
    RCC->CFGR = 0x00000000;         /* CFGR清零 */
    RCC->D1CFGR = 0x00000000;       /* D1CFGR清零 */
    RCC->D2CFGR = 0x00000000;       /* D2CFGR清零 */
    RCC->D3CFGR = 0x00000000;       /* D3CFGR清零 */
    RCC->PLLCKSELR = 0x00000000;    /* PLLCKSELR清零 */
    RCC->PLLCFGR = 0x00000000;      /* PLLCFGR清零 */
    RCC->CIER = 0x00000000;         /* CIER清零, 禁止所有RCC相关中断 */

    PWR->CR3 &= ~(1 << 2);      /* SCUEN = 0, 锁定LDOEN和BYPASS位的设置 */
    PWR->D3CR |= 3 << 14;       /* VOS = 3, Scale1, 1.2V内核电压,FLASH访问可以得到最高性能 */
    /* 480M版本H750芯片（V版本）新增Scale0设置,在进入睡眠模式之前, 必须先退出Scale0模式！！ */
    RCC->APB4ENR |= 1 << 1;     /* 使能SYSCFGEN位 */
    SYSCFG->PWRCR |= 1 << 0;    /* 设置ODEN位为1, 使能Overdrive,此时VCORE = 1.35V */

    while ((PWR->D3CR & (1 << 13)) == 0);   /* 等待电压稳定 */

    RCC->CR |= 1 << 16; /* HSEON = 1, 开启HSE */

    while (((RCC->CR & (1 << 17)) == 0) && (retry < 0X7FFF)) {
        retry++;        /* 等待HSE RDY */
    }

    if (retry == 0X7FFF) {
        retval = 1;     /* HSE无法就绪 */
    }
    else {
        RCC->PLLCKSELR |= 2 << 0;           /* PLLSRC[1:0] = 2, 选择HSE作为PLL的输入时钟源 */
        RCC->PLLCKSELR |= CLOCK_DIVM1 << 4;        /* DIVM1[5:0] = pllm, 设置PLL1的预分频系数 */
        RCC->PLL1DIVR |= (CLOCK_PLL1_DIVN - 1) << 0;   /* DIVN1[8:0] = plln - 1, 设置PLL1的倍频系数, 设置值需减1 */
        RCC->PLL1DIVR |= (CLOCK_PLL1_DIVP - 1) << 9;   /* DIVP1[6:0] = pllp - 1, 设置PLL1的p分频系数, 设置值需减1 */
        RCC->PLL1DIVR |= (CLOCK_PLL1_DIVQ - 1) << 16;  /* DIVQ1[6:0] = pllq - 1, 设置PLL1的q分频系数, 设置值需减1 */
        RCC->PLL1DIVR |= (CLOCK_PLL1_DIVR - 1) << 24;           /* DIVR1[6:0] = pllr - 1, 设置PLL1的r分频系数, 设置值需减1, r分频出来的时钟没用到 */
        RCC->PLLCFGR |= 2 << 2;             /* PLL1RGE[1:0] = 2, PLL1输入时钟频率在4~8Mhz之间(25 / 5 = 5Mhz), 如修改pllm, 请确认此参数 */
        RCC->PLLCFGR |= 0 << 1;             /* PLL1VCOSEL = 0, PLL1中的VCO范围, 192~836Mhz(实际可以到960, 以满足480M主频设置要求) */
        RCC->PLLCFGR |= 3 << 16;            /* DIVP1EN = 1, DIVQ1EN = 1, 使能pll1_p_ck和pll1_q_ck */
        RCC->CR |= 1 << 24;                 /* PLL1ON = 1, 使能PLL1 */
        retry = 0;

        while ((RCC->CR & (1 << 25)) == 0) { /* PLL1RDY = 1?, 等待PLL1准备好 */
            retry++;

            if (retry > 0X1FFFFF) {
                retval = 2; /* PLL1无法就绪 */
                break;
            }
        }

        /* 设置PLL2的R分频输出, 为220Mhz, 后续做TFTLCD时钟, 可得到220M的fmc_ker_ck时钟频率 */
        RCC->PLLCKSELR |= 25 << 12;         /* DIVM2[5:0] = 25, 设置PLL2的预分频系数 */
        RCC->PLL2DIVR |= (440 - 1) << 0;    /* DIVN2[8:0] = 440 - 1, 设置PLL2的倍频系数, 设置值需减1 */
        RCC->PLL2DIVR |= (2 - 1) << 9;      /* DIVP2[6:0] = 2 - 1, 设置PLL2的p分频系数, 设置值需减1 */
        RCC->PLL2DIVR |= (2 - 1) << 24;     /* DIVR2[6:0] = 2 - 1, 设置PLL2的r分频系数, 设置值需减1 */
        RCC->PLLCFGR |= 0 << 6;             /* PLL2RGE[1:0] = 0, PLL2输入时钟频率在1~2Mhz之间(25/25 = 1Mhz) */
        RCC->PLLCFGR |= 0 << 5;             /* PLL2VCOSEL = 0, PLL2宽的VCO范围, 192~836Mhz */
        RCC->PLLCFGR |= 1 << 19;            /* DIVP2EN = 1, 使能pll2_p_ck */
        RCC->PLLCFGR |= 1 << 21;            /* DIVR2EN = 1, 使能pll2_r_ck */
        RCC->D1CCIPR &= ~(3 << 0);          /* 清除FMCSEL[1:0]原来的设置 */
        RCC->D1CCIPR |= 2 << 0;             /* FMCSEL[1:0] = 2, FMC时钟来自于pll2_r_ck */
        RCC->CR |= 1 << 26;                 /* PLL2ON = 1, 使能PLL2 */
        retry = 0;

        while ((RCC->CR & (1 << 27)) == 0) { /* PLL2RDY = 1?, 等待PLL2准备好 */
            retry++;

            if (retry > 0X1FFFFF) {
                retval = 3; /* PLL2无法就绪 */
                break;
            }
        }

        RCC->D1CFGR |= 8 << 0;              /* HREF[3:0] = 8, rcc_hclk1/2/3/4  =  sys_d1cpre_ck / 2 = 480 / 2 = 240Mhz, 即AHB1/2/3/4 = 240Mhz */
        RCC->D1CFGR |= 0 << 8;              /* D1CPRE[2:0] = 0, sys_d1cpre_ck = sys_clk/1 = 480 / 1 = 480Mhz, 即CPU时钟 = 480Mhz */
        RCC->CFGR |= 3 << 0;                /* SW[2:0] = 3, 系统时钟(sys_clk)选择来自pll1_p_ck, 即480Mhz */
        retry = 0;

        while (swsval != 3) {                /* 等待成功将系统时钟源切换为pll1_p_ck */
            swsval = (RCC->CFGR & (7 << 3)) >> 3;   /* 获取SWS[2:0]的状态, 判断是否切换成功 */
            retry++;

            if (retry > 0X1FFFFF) {
                retval = 4; /* 无法切换时钟 */
                break;
            }
        }

        FLASH->ACR |= 2 << 0;               /* LATENCY[2:0] = 2, 2个CPU等待周期(@VOS1 Level, maxclock = 210Mhz) */
        FLASH->ACR |= 2 << 4;               /* WRHIGHFREQ[1:0] = 2, flash访问频率<285Mhz */
        RCC->D1CFGR |= 4 << 4;              /* D1PPRE[2:0] = 4,  rcc_pclk3 = rcc_hclk3/2 = 120Mhz, 即APB3 = 120Mhz */
        RCC->D2CFGR |= 4 << 4;              /* D2PPRE1[2:0] = 4, rcc_pclk1 = rcc_hclk1/2 = 120Mhz, 即APB1 = 120Mhz */
        RCC->D2CFGR |= 4 << 8;              /* D2PPRE2[2:0] = 4, rcc_pclk2 = rcc_hclk1/2 = 120Mhz, 即APB2 = 120Mhz */
        RCC->D3CFGR |= 4 << 4;              /* D3PPRE[2:0] = 4,  rcc_pclk4 = rcc_hclk4/2 = 120Mhz, 即APB4 = 120Mhz */
        
        RCC->CR |= 1 << 7;                  /* CSION = 1, 使能CSI, 为IO补偿单元提供时钟 */
        RCC->APB4ENR |= 1 << 1;             /* SYSCFGEN = 1, 使能SYSCFG时钟 */
        SYSCFG->CCCSR |= 1 << 0;            /* EN = 1, 使能IO补偿单元 */
    }

    SCB_EnableICache(); /* 使能I-Cache,函数在core_cm7.h里面定义 */
    SCB_EnableDCache(); /* 使能D-Cache,函数在core_cm7.h里面定义 */
    SCB->CACR |= 1 << 2;/* 强制D-Cache透写,如不开启透写,实际使用中可能遇到各种问题 */
}


extern size_t eb_system_heap;

static void heap_init(void)
{
    void* start = &eb_system_heap;
	kmem_init(start, CONFIG_SRAM_SIZE - CONFIG_STACK_SIZE - ((uint32_t)start - SRAM_D1_START_ADDR));
    kmem_add_pool((void*)SRAM_D2_START_ADDR, SRAM_D2_SIZE);
    kmem_add_pool((void*)SRAM_D3_START_ADDR, SRAM_D3_SIZE);
    kmem_add_pool((void*)SRAM_ITCM_START_ADDR, SRAM_ITCM_SIZE);
    kmem_add_pool((void*)SRAM_DTCM_START_ADDR, SRAM_DTCM_SIZE);
}


void eb_machine_init()
{
    clock_init();
    heap_init();
}
